package com.ota.client.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ota.client.api.IFirmwareDownloader;
import com.ota.client.api.IUpgrate;
import com.ota.client.api.UpgradeStatus;
import com.ota.client.api.UpgrateInfo;
import com.ota.client.config.OTAConfig;
import com.ota.client.dto.CheckResponse;
import com.ota.client.tools.JWT;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class FirmwareDownloader implements IFirmwareDownloader {
    private OTAConfig config = new OTAConfig();
    private  ScheduledExecutorService scheduler=Executors.newSingleThreadScheduledExecutor();
    
    @Override
    public IFirmwareDownloader productId(String productId) {
        config.getDevice().setProductId(productId);
        return this;
    }

    @Override
    public IFirmwareDownloader serialNumber(String sn) {
        config.getDevice().setSn(sn);
        return this;
    }

    @Override
    public IFirmwareDownloader server(String server) {
        config.getServer().setUrl(server);
        return this;
    }

    @Override
    public IFirmwareDownloader key(String key) {
        config.getDevice().setKey(key);
        return this;
    }

    @Override
    public IFirmwareDownloader tempDir(String tempDir) {
        config.getClient().setTempDir(tempDir);
        return this;
    }

    @Override
    public IFirmwareDownloader timeout(int timeout) {
        config.getClient().setDownloadTimeout(timeout);
        return this;
    }

    @Override
    public IFirmwareDownloader interval(int interval) {
        config.getClient().setCheckInterval(interval);
        return this;
    }

    private void dowatch(Client client,IUpgrate upgrate){
        CheckResponse checkResp =client.checkUpdate();
                        if (checkResp == null||!checkResp.isNeedUpdate()) {
                            //upgrate.log("无需升级");
                            return;
                        }
                        upgrate.log("发现新版本: {} -> {}", 
                        checkResp.getCurrentVersion(), checkResp.getLatestVersion());
                        upgrate.log("固件描述: {}", checkResp.getFirmwareInfo().getDescription());
                        upgrate.log("文件大小: {} bytes", checkResp.getFirmwareInfo().getFileSize());
                        try {
                            client.ensureTempDir();
                        } catch (IOException e) {
                            upgrate.log("create tmpdir failed!", e);
                        }
                        
                        String firmwarePath = client.downloadFirmware(checkResp.getFirmwareInfo().getDownloadUrl());
                        if (firmwarePath == null) {
                            client.reportUpgradeStatus(checkResp.getLatestVersion(), "failed", "下载失败");
                            upgrate.log("下载固件失败");
                            return;
                        }
                        if (!client.verifyFirmware(firmwarePath)) {
                            client.reportUpgradeStatus(checkResp.getLatestVersion(), "failed", "签名验证失败");
                            return;
                        }
                        UpgrateInfo info = upgrate.upgrade(firmwarePath, checkResp.getLatestVersion());
                        client.cleanTempFirmware(firmwarePath);
                        if(info.getStatus() != UpgradeStatus.COMPLETED){
                            client.reportUpgradeStatus(checkResp.getLatestVersion(), "failed", info.getMessage());
                            return;
                        }
                        client.reportUpgradeStatus(checkResp.getLatestVersion(), "success", "升级成功");
                        upgrate.log("固件升级成功，当前版本: {}", checkResp.getLatestVersion());
                        // 更新配置中的版本号
                        config.getDevice().setVersion(checkResp.getLatestVersion());

    }
    @Override
    public IFirmwareDownloader startWatch(IUpgrate upgrate) {
                final Client client = new Client(config);
                config.getDevice().setVersion(upgrate.version());
                client.setLogger((String fString, Object... args)->{
                    upgrate.log(fString, args);
                });
                dowatch(client,upgrate);
                scheduler.scheduleAtFixedRate(
                new Runnable() {
                    @Override
                    public void run() {
                        dowatch(client,upgrate);
                    }
                },
                config.getClient().getCheckInterval(),
                config.getClient().getCheckInterval(),
                TimeUnit.SECONDS
        );
        return this;
    }

    @Override
    public void stopWatch() {
        scheduler.shutdown();
        try {
            if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

class Client { 
    private static final String HMAC_SHA512 = "HmacSHA512";
    private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    
    private  OTAConfig config;
    private  OkHttpClient httpClient;
    private  ObjectMapper objectMapper;
    private Logger logger=new Logger(){
        public void log(String fString, Object... args) {

        }
    };

    public void setLogger(Logger logger) {
        this.logger = logger;
    }
    public Client(OTAConfig config) {
        this.config = config;
        this.httpClient = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .readTimeout(config.getClient().getDownloadTimeout(), TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .build();
        this.objectMapper = new ObjectMapper();
    }
    
    public CheckResponse checkUpdate() {
        try {
            String url = config.getServer().getUrl() + "/ota/check";
            
            Map<String, String> requestData = new HashMap<String, String>();
            requestData.put("product_id", config.getDevice().getProductId());
            requestData.put("current_version", config.getDevice().getVersion());
            requestData.put("sn", config.getDevice().getSn());
            
            String jsonData = objectMapper.writeValueAsString(requestData);
            
            RequestBody body = RequestBody.create(JSON, jsonData);
            Request request = new Request.Builder()
                    .url(url)
                    .header("Authorization", "Bearer " + new JWT()
                    .key(config.getDevice().getKey())
                    .hs512().payload("pid", config.getDevice().getProductId())
                    .payload("sn", config.getDevice().getSn())
                    .payload("v", config.getDevice().getVersion())
                    .genJwt()
                    )
                    .post(body)
                    .build();
            
            //logger.log("发送检查更新请求: {}", jsonData);
            
            Response response = httpClient.newCall(request).execute();
            try {
                if (!response.isSuccessful()) {
                    logger.log("服务器返回错误: url:{} code:{} {}", url,response.code(), response.body().string());
                    return null;
                }
                
                String responseBody = response.body().string();
                //logger.log("服务器返回: {}", responseBody);
                return objectMapper.readValue(responseBody, CheckResponse.class);
            } finally {
                response.close();
            }
            
        } catch (Exception e) {
            e.printStackTrace();
            //logger.error("检查更新失败", e);
            return null;
        }
    }

     public boolean downloadSignature(String firmwarePath, String signaturePath) {
        try {
            // 构建签名文件URL
            String firmwareURL = Paths.get(firmwarePath).getFileName().toString();
            String signatureURL = firmwareURL + ".sum";
            
            // 从固件文件名中提取产品ID
            String[] parts = firmwareURL.split("_");
            if (parts.length < 2) {
                //logger.error("无法从固件文件名解析产品ID");
                return false;
            }
            String productID = parts[0];
            
            String url = config.getServer().getUrl() + "/ota/download/" + productID + "/" + signatureURL;
            
            Request request = new Request.Builder().
            url(url)
            .header("Authorization", "Bearer " + new JWT()
                    .key(config.getDevice().getKey())
                    .hs512().payload("pid", config.getDevice().getProductId())
                    .payload("sn", config.getDevice().getSn())
                    .payload("v", config.getDevice().getVersion())
                    .genJwt()
            )
            .build();
            
            Response response = httpClient.newCall(request).execute();
            try {
                if (!response.isSuccessful()) {
                    //logger.error("下载签名失败，状态码: {}", response.code());
                    return false;
                }
                
                // 保存签名文件
                InputStream inputStream = response.body().byteStream();
                try {
                    Files.copy(inputStream, Paths.get(signaturePath), StandardCopyOption.REPLACE_EXISTING);
                } finally {
                    inputStream.close();
                }
                
                return true;
            } finally {
                response.close();
            }
            
        } catch (Exception e) {
            //logger.error("下载签名文件失败", e);
            return false;
        }
    }

    public boolean verifyFirmware(String firmwarePath) {
        try {
            // 读取固件文件
            byte[] firmwareData = Files.readAllBytes(Paths.get(firmwarePath));
            
            // 下载签名文件
            String signaturePath = firmwarePath + ".sum";
            if (!downloadSignature(firmwarePath, signaturePath)) {
                return false;
            }
            
            // 读取签名
            String expectedSignature = new String(Files.readAllBytes(Paths.get(signaturePath))).trim();
            
            // 计算实际签名
            String actualSignature = calculateHMAC(firmwareData);
            
            if (!actualSignature.equals(expectedSignature)) {
                //logger.error("签名验证失败: 期望 {}, 实际 {}", expectedSignature, actualSignature);
                return false;
            }
            
            //logger.info("固件签名验证成功");
            return true;
            
        } catch (Exception e) {
            //logger.error("固件签名验证失败", e);
            return false;
        }
    }
    public String calculateHMAC(byte[] data) throws NoSuchAlgorithmException, InvalidKeyException {
        Mac mac = Mac.getInstance(HMAC_SHA512);
        SecretKeySpec secretKeySpec = new SecretKeySpec(config.getDevice().getKey().getBytes(), HMAC_SHA512);
        mac.init(secretKeySpec);
        byte[] hmacBytes = mac.doFinal(data);
        
        // 转换为十六进制字符串
        StringBuilder sb = new StringBuilder();
        for (byte b : hmacBytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
    public void reportUpgradeStatus(String version, String status, String message) {
        try {
            String url = config.getServer().getUrl() + "/ota/report";
            
            Map<String, String> requestData = new HashMap<String, String>();
            requestData.put("sn", config.getDevice().getSn());
            requestData.put("product_id", config.getDevice().getProductId());
            requestData.put("version", version);
            requestData.put("status", status);
            requestData.put("message", message);
            
            String jsonData = objectMapper.writeValueAsString(requestData);
            
            RequestBody body = RequestBody.create(JSON, jsonData);
            Request request = new Request.Builder()
                    .url(url)
                    .header("Authorization", "Bearer " + new JWT()
                    .key(config.getDevice().getKey())
                    .hs512().payload("pid", config.getDevice().getProductId())
                    .payload("sn", config.getDevice().getSn())
                    .payload("v", config.getDevice().getVersion())
                    .genJwt()
                    )
                    .post(body)
                    .build();
            
            //logger.debug("发送升级状态报告: {}", jsonData);
            
            Response response = httpClient.newCall(request).execute();
            try {
                if (!response.isSuccessful()) {
                    //logger.error("报告状态失败: {} {}", response.code(), response.body().string());
                    return;
                }
                
                //logger.info("升级状态报告成功");
            } finally {
                response.close();
            }
            
        } catch (Exception e) {
            //logger.error("报告升级状态失败", e);
        }
    }
    
    /**
     * 确保临时目录存在
     */
    public void ensureTempDir() throws IOException {
        Path tempPath = Paths.get(config.getClient().getTempDir());
        if (!Files.exists(tempPath)) {
            Files.createDirectories(tempPath);
        }
    }

    public void cleanTempFirmware(String firmwarePath) {
        try {
            new File(firmwarePath).delete();
            new File(firmwarePath+".sum").delete();
        } catch (Exception e) {
            //logger.error("清理临时目录失败", e);
        }
    }

    public String downloadFirmware(String downloadURL) {
        try {
            String url = config.getServer().getUrl() + "/ota/download/" + downloadURL;
            //logger.info("开始下载固件: {}", url);
            
            Request request = new Request.Builder().url(url)
            .header("Authorization", "Bearer " + new JWT()
                    .key(config.getDevice().getKey())
                    .hs512().payload("pid", config.getDevice().getProductId())
                    .payload("sn", config.getDevice().getSn())
                    .payload("v", config.getDevice().getVersion())
                    .genJwt()
                    )
            .build();
            
            Response response = httpClient.newCall(request).execute();
            try {
                if (!response.isSuccessful()) {
                    //logger.error("下载失败，状态码: {}", response.code());
                    return null;
                }
                
                // 创建本地文件
                String filename = Paths.get(downloadURL).getFileName().toString();
                if (filename.isEmpty()) {
                    filename = "firmware.bin";
                }
                
                Path localPath = Paths.get(config.getClient().getTempDir(), filename);
                
                // 保存文件
                InputStream inputStream = response.body().byteStream();
                try {
                    Files.copy(inputStream, localPath, StandardCopyOption.REPLACE_EXISTING);
                } finally {
                    inputStream.close();
                }
                
                //logger.info("固件下载完成: {} ({} bytes)", localPath, Files.size(localPath));
                return localPath.toString();
            } finally {
                response.close();
            }
            
        } catch (Exception e) {
            //logger.error("下载固件失败", e);
            return null;
        }
    }
}
interface Logger  { 
    void log(String fString, Object... args);
}
